package model;

import java.util.HashSet;
import java.util.Set;
import java.util.Stack;

public class Tablero {

	private final int dimX;
	private final int dimY;
	/**
	 * La matriz utilizada para representar el estado interno del tablero
	 * tiene dos funciones: indicar si una posición está ocupada y, 
	 * si lo está, informar qué variante la está ocupando.
	 */
	private final Variante[][] matriz;
	/**
	 * Par ordenado que indica el cuadrado objetivo en la operatoria 
	 * de relleno con variantes de piezas.
	 */
	private int[] primeroLibre;
	private Set<Pieza> piezasIncluidas;
	private Stack<Variante> variantesUtilizadas;

	/**
	 * Crea el tablero con la dimensión provista.
	 * @param dimX
	 */
	public Tablero(final int dimX) {
		super();
		this.dimX = dimX;
		final int cuadradosTotales = 
				Pieza.CUADRADOS_POR_PIEZA * Pieza.PIEZAS_TOTALES;
		if (cuadradosTotales % dimX != 0) {
			throw new IllegalArgumentException(
					"El tablero no puede tomar tal dimensión! ("
					+ dimX + ")");
		}
		dimY = cuadradosTotales / dimX;
		matriz = new Variante[this.dimX][this.dimY];
		primeroLibre = new int[] { 0, 0 };
		piezasIncluidas = new HashSet<Pieza>();
		variantesUtilizadas = new Stack<Variante>();
	}

	/**
	 * Evalúa si el tablero está completo.
	 * @return true si se llenó, false en el otro caso.
	 */
	public boolean estaLleno() {
		return Pieza.PIEZAS_TOTALES == piezasIncluidas.size();
	}

	/**
	 * Inserta en el tablero una variante. Chequea si es posible hacerlo,
	 * y de lograrlo actualiza el estado del tablero para la próxima 
	 * inserción.
	 * @param variante a insertar
	 * @return true si logró insertarla, false si no fue posible.
	 */
	public boolean insertar(final Variante variante) {
		// Si la pieza no fue previamente incluida,
		if (!piezasIncluidas.contains(variante.getPieza())) {
			// se trata de encajarla en la matriz.
			boolean encajado = encajarEnMatriz(variante);
			// Si se encajó,
			if (encajado) {
				// anotamos la variante utilizada,
				variantesUtilizadas.push(variante);
				// anotamos la pieza, para no repetirla,
				piezasIncluidas.add(variante.getPieza());
				// y actualizamos el primer cuadrado libre, objetivo 
				// de la inserción.
				updatePrimeroLibre();
			}
			return encajado;
		}
		return false;
	}

	public synchronized void mostrarParaDebug() {
		System.out.println(this.toString());
		try {
			wait(50);
		} catch (InterruptedException e) {
			// No pasa.
		}
	}

	/**
	 * Busca encajar las posiciones de la variante en el tablero, a partir del
	 * primer cuadrado libre del mismo al momento de la inserción.
	 * @param variante a encajar
	 * @return true si logró encajarla, false si no fue posible.
	 */
	private boolean encajarEnMatriz(final Variante variante) {
		final int[][] indicaciones = variante.getIndicaciones();
		int x, y;
		boolean esPosible = true;
		// Para todas las indicaciones de la variante,
		for (int[] indicacion : indicaciones) {
			// calculando la posiciones absolutas desde el primero libre,
			x = primeroLibre[0] + indicacion[0];
			y = primeroLibre[1] + indicacion[1];
			// se guarda si es la posición indicada estaría en rango y libre;
			esPosible = (x < dimX) && (x >= 0) 
					&& (y < dimY) && (y >= 0)
					&& (matriz[x][y] == null);
			// siempre y cuando siga siendo posible!
			if (!esPosible) break;
		}
		// Si es posible insertar todos los cuadrados de la pieza,
		if (esPosible) {
			// se guarda en la matriz la referencia de la variante que 
			// ocupará el primer cuadrado libre,
			matriz[primeroLibre[0]][primeroLibre[1]] = variante;
			// y para todas las indicaciones de la variante,
			for (int[] indicacion : indicaciones) {
				// se hace lo propio.
				x = primeroLibre[0] + indicacion[0];
				y = primeroLibre[1] + indicacion[1];
				matriz[x][y] = variante;
			}
		}
		return esPosible;
	}

	/**
	 * Remueve la última variante de pieza insertada en el tablero.
	 */
	public void removerUltima() {
		// Desde la pila se obtiene la última introducida.
		final Variante variante = variantesUtilizadas.pop();
		// Y de toda la matriz, esa variante es limpiada.
		for (int x = 0; x < dimX; x++) {
			for (int y = 0; y < dimY; y++) {
				if (matriz[x][y] == variante) {
					matriz[x][y] = null;
				}
			}
		}
		// Se la remueve de entre las piezas incluidas en el tablero,
		piezasIncluidas.remove(variante.getPieza());
		// y se actualiza el primer cuadrado libre, que estaría más atrás.
		updatePrimeroLibre();
	}

	/**
	 * Actualiza el primer cuadrado libre desde "arriba a la izquierda".
	 * La estrategia se apoya en insertar variantes desde este punto.
	 */
	protected void updatePrimeroLibre() {
		// Para todas las filas,
		for (int x = 0; x < dimX; x++) {
			// y todas las columnas,
			for (int y = 0; y < dimY; y++) {
				// si hay uno libre,
				if (matriz[x][y] == null) {
					// se queda con esa posición, la guarda,
					primeroLibre = new int[] { x, y };
					// y sale.
					return;
				}
			}
		}
	}

	protected Variante[][] getMatriz() {
		return matriz;
	}

	@Override
	public String toString() {
		final StringBuffer buffer = new StringBuffer();
		Variante variante = null;
		for (int i = 0; i < dimY+2; i++) buffer.append("-");
		buffer.append("\n");
		for (int i = 0; i < dimX; i++){
			buffer.append("|");
			for (int j = 0; j < dimY; j++) {
				variante = matriz[i][j];					
				buffer.append(variante != null ? 
						variante.getFichaVariada().getNombre() : " ");
			}
			buffer.append("|\n");
		}
		for (int i = 0; i < dimY+2; i++) buffer.append("-");
		buffer.append("\n");
		return buffer.toString();
	}
	
}
